#include <linux/module.h>
#include <linux/skbuff.h>
#include <net/ipv6.h>
#include <net/ip.h>
#include <linux/netfilter/x_tables.h>
#include <linux/netfilter/nf_conntrack_common.h>
#include <linux/netfilter/nf_conntrack_tuple_common.h>
#include <net/netfilter/nf_conntrack_acct.h>
#include <net/netfilter/nf_conntrack.h>

#include "xt_wx.h"
//#define WX_DEBUG
#if 0
static void hex_dump(const unsigned char *buf, size_t len)
{
    size_t i;
    for (i = 0; i < len; i++)
    {
        if (i && !(i % 16))
        printk("\n");
        printk("%02x ", *(buf + i));
    }
    printk("\n");
}
#endif
static inline bool unpack_packet(const struct sk_buff *skb, const struct iphdr *iph,
    const u_char **data, u_int16_t *datalen, u_int8_t *direction)
    {
        if (iph->protocol == IPPROTO_TCP)
        {
            struct tcphdr *tcph;
            tcph = (void*)iph + (iph->ihl << 2);
            *data = (void *)tcph + (tcph->doff << 2);
        }
        /*
        else if (iph->protocol == IPPROTO_UDP)
        {
        struct udphdr *udph;
        udph = (void*)iph + (iph->ihl << 2);
        *data = (void *)udph + 8;
    }
    */
    else
    return false;

    *direction = CTINFO2DIR(skb->nfctinfo);
    *datalen = __constant_ntohs(iph->tot_len) - ((void *)*data - (void *)iph);

    if (likely((int16_t)*datalen > 0))
    return true;
    else
    return false;
}

#define	WX_TCP_START_PACKET	    4
#define	WX_TCP_END_PACKET	    40
#define	WX_TCP_DSIZE_MIN	    16
#define	WX_TCP_DSIZE_MAX	    1024

#define	WX_HTTP_START_PACKET	3
#define	WX_HTTP_END_PACKET	    8
#define	WX_HTTP_DSIZE_MIN		128
#define	WX_HTTP_DSIZE_MAX		1460
#define	WX_HTTP_NEEDLE	        "POST http://short.weixin.qq.com/cgi-bin/micromsg-bin/"
#define	WX_HTTP_NEEDLE_LEN      (sizeof(WX_HTTP_NEEDLE) - 1)
#define	WX_HTTP_NEEDLE1	        "POST http://szshort.weixin.qq.com/cgi-bin/micromsg-bin/"
#define	WX_HTTP_NEEDLE1_LEN      (sizeof(WX_HTTP_NEEDLE1) - 1)

#define WX_HEADER_SIZE		    0x0010
#define WX_THX_VER		        0x0001
#if 0
#define WX_HTTP_STR             "mmsnstimeline"
#define WX_HTTP_STR_LEN         (sizeof(WX_HTTP_STR) - 1)
#endif
static inline bool im_wx_mt(const u_char *data, int datalen, const struct xt_wx_uint32_info *info)
{
    wx_header_t *wxhdr = (struct wx_header_s *)data;

    /* Check if the packet is MicroMessage */
    if (ntohl(wxhdr->packet_len) != datalen)
    return false;
    if (ntohs(wxhdr->header_len) != WX_HEADER_SIZE)
    return false;
    if (ntohs(wxhdr->thx_ver) != WX_THX_VER)
    return false;
    #ifdef WX_DEBUG
    printk("wx:len:%08x %08x header_len:%04x thx_ver:%04x opcode:[%08x]\n",
    datalen,
    ntohl(wxhdr->packet_len),
    ntohs(wxhdr->header_len),/* 2字节用ntohs */
    ntohs(wxhdr->thx_ver),
    ntohl(wxhdr->operation_code)
);
#endif // WX_DEBUG

if ( ntohl(wxhdr->operation_code) == info->operation_code)
return true;
return false;
}

static inline bool im_wx_http_mt(const char *data, int datalen, const struct xt_wx_string_info *info)
{
    const char *p;
    char action[XT_WX_MAX_STR_LENG] = {'\0'};
    int step;
    /* Check if the packet is MicroMessage */
    if (strncasecmp(data, WX_HTTP_NEEDLE, WX_HTTP_NEEDLE_LEN) == 0 )
    {
        p = data + WX_HTTP_NEEDLE_LEN;
        datalen -= WX_HTTP_NEEDLE_LEN;
    }
    else if( strncasecmp(data, WX_HTTP_NEEDLE1, WX_HTTP_NEEDLE1_LEN) == 0){
        p = data + WX_HTTP_NEEDLE1_LEN;
        datalen -= WX_HTTP_NEEDLE1_LEN;
    }
    else
    {
        return false;
    }
    /*((*p >= '0' && *p <= '9')||(*p >= 'A' && *p <= 'z'))*/
    for (step = 0; *p != ' ' && step < XT_WX_MAX_STR_LENG; step++,p++,datalen--)
    action[step] = *p;

    if (step == 0 || strlen(action) == 0)
    return false;

    if (strncasecmp(action,info->operation_code, strlen(info->operation_code)) == 0)
    {
        #ifdef WX_DEBUG
        printk("wx %s match\n",action);
        #endif
        return true;
    }
    else
    {
        #ifdef WX_DEBUG
        printk("wx %s missmatch\n",action);
        #endif
        return false;
    }
}

static bool wx_mt (const struct sk_buff *skb, struct xt_action_param *par)
{
    const struct iphdr           *iph;
    const struct nf_conn 		 *ct;
    enum ip_conntrack_info		 ctinfo;
    const struct nf_conn_counter *counters;
    u_int16_t		packets;
    const u_char	*data;
    u_int16_t		datalen;
    u_int8_t		direction;

    iph = ip_hdr(skb);

    if (unlikely(iph->protocol != IPPROTO_TCP)) return false;

    /*skb_is_nonlinear用于检查缓冲区是否为片段
    * skb_linearize用于将几个片段压合为一个缓冲区，将引发拷贝从而使性能下降
    */
    if (unlikely(skb_is_nonlinear(skb))) return false;

    /* 函数nf_ct_get可以找到数据包对应的连接,如果存在,那么将会返回这个连接并且返回连接状态 */

    if (unlikely(!(ct = nf_ct_get(skb, &ctinfo)) ||
    nf_ct_is_untracked(ct))) return false;

    if (unlikely(!(counters = nf_conn_acct_find(ct)))) return false;

    if (unlikely(!unpack_packet(skb, iph, &data, &datalen, &direction))) return false;

    packets = counters[IP_CT_DIR_ORIGINAL].packets + counters[IP_CT_DIR_REPLY].packets;

    if (direction != IP_CT_DIR_ORIGINAL)
    return false;

    switch (((struct xt_wx_info *)par->matchinfo)->wx_op_type) {
        case WX_STR_T:
        return im_wx_http_mt(data, datalen, &((struct xt_wx_info *)par->matchinfo)->op_str);

        case WX_CODE_T:
        return im_wx_mt(data, datalen, &((struct xt_wx_info *)par->matchinfo)->op_code);
        default:
        return false;
    }
}

static struct xt_match wx_mt_reg[] __read_mostly = {
    {
        .name      = "wx",
        .family    = NFPROTO_IPV4,
        .match     = wx_mt,
        .matchsize = sizeof(struct xt_wx_info),
        .me        = THIS_MODULE,
    },
};

static int __init wx_mt_init(void)
{
    return xt_register_matches(wx_mt_reg, ARRAY_SIZE(wx_mt_reg));
}

static void __exit wx_mt_exit(void)
{
    xt_unregister_matches(wx_mt_reg, ARRAY_SIZE(wx_mt_reg));
}

MODULE_AUTHOR("Candoit");
MODULE_DESCRIPTION("Weixin TCP or POST opcode Match");
MODULE_LICENSE("GPL");

module_init(wx_mt_init);
module_exit(wx_mt_exit);
